package com.bite.system.service.sysuser;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.bite.common.core.constants.HttpConstants;
import com.bite.common.core.domain.LoginUser;
import com.bite.common.core.domain.R;
import com.bite.common.core.domain.vo.LoginUserVO;
import com.bite.common.core.enums.ResultCode;
import com.bite.common.core.enums.UserIdentity;
import com.bite.common.security.exception.ServiceException;
import com.bite.common.security.service.TokenService;
import com.bite.system.domain.sysuser.SysUser;
import com.bite.system.domain.sysuser.dto.SysUserSaveDTO;
import com.bite.system.mapper.sysuser.SysUserMapper;
import com.bite.system.utils.BCryptUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;

import java.util.List;

@RefreshScope  //（不需要重写这个secret，假如我们下次更新的时候）
//swagger生成接口文档
@Service
public class SysUserServiceImpl implements  ISysUserService{
    @Autowired
    private SysUserMapper sysUserMapper;
    @Value("${jwt.secret}")
    private String secret;
    @Autowired
    public TokenService tokenService;

    @Override
    public R<String> login(String userAccount, String password) {
        //int a=100/0; 测试异常的数据
        //通过账号去数据库查询，对应用户的信息
        //参数通过lambda表达式传过来,要把值和参数全装进表达式中
        LambdaQueryWrapper<SysUser>queryWrapper=new LambdaQueryWrapper<>();
        //select来构建查询方法 ：select password from sysy_user where user_account-#{userAccount}
        //这一步查出来的就是密文 SysUser::getUserid,
       SysUser sysUser=
               sysUserMapper.selectOne(queryWrapper
                       .select(SysUser::getUserId,SysUser::getPassword,SysUser::getNickName)
               .eq(SysUser::getUserAccount,userAccount));
     //   R loginResult=new R();
        if(sysUser==null) {
//            loginResult.setCode(ResultCode.FAILED_USER_NOT_EXISTS.getCode()); //0代表失败
//            loginResult.setMsg(ResultCode.FAILED_USER_NOT_EXISTS.getMsg());
            //        return loginResult;
            return  R.fail(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if(BCryptUtils.matchPassword(password,sysUser.getPassword())){
//     /**      loginResult.setCode(ResultCode.SUCCESS.getCode());
//           loginResult.setMsg(ResultCode.SUCCESS.getMsg());
    //        return loginResult;

            /**
             * JWT的使用
             *       Map<String,Object> claims=new HashMap<>();
             *             String userKey=UUID.fastUUID().toString();
             *             claims.put("userId",sysUser.getUserid());
             *             claims.put("userKey",userKey);
             *           String token=JwtUtils.createToken(claims,secret);
             *
             *             //生成一个UUID
             *             //第三方机制中存储敏感信息,存储身份认证需要的信息，账号和密码
             *             // 使用什么样子数据结构 identity  1表示普通用户，2表示管理员用户 搞一个对象会更加方便，变更的话，加一个属性就好了
             *             //看起来key,value.一般hash,String,,zset,set,list,String（五种),(我们自己配置了一个序列化器)
             *             //序列化器，完成序列化之后(我之前做的那个博客，把对象序列化成字符串，然后放入redis）
             *             // key保证唯一，并且便于维护，最好一个统一的前缀，Logintoken ,作为一个key的前缀，登录用户可能多个，
             *             //LoginToken+userId (因为UserId本身就是唯一的，通过雪花算法)
             *             //但是我也可以使用别的，比如我们这个有了唯一ID,但是我们后续，也该考虑一下，假如后面又来一个需要唯一的怎么办
             *             //管理员   C端（假如都用自增ID的时候，没法处理)
             *
             *  String key= CacheConstants.LOGIN_TOKEN_KEY +userKey;//拼一个前缀方便统一管理
             *             LoginUser loginUser=new LoginUser();
             *             loginUser.setIdentity(UserIdentity.ADMIN.getValue());
             *             //过期时间的处理，720分钟-12个小时,用户的竞赛会参加 (2-3h)
             *             redisService.setCacheObject(key,loginUser,CacheConstants.EXP, TimeUnit.MILLISECONDS);
             *             //假如用户拿着过期的token,我们需要判断什么是否过期，通过redis是否过期来判断。JWT上加一个参数
             */
                return R.ok(tokenService.createToken(sysUser.getUserId(),
                                secret,
                               UserIdentity.ADMIN.getValue(),
                               sysUser.getNickName(),null));
        }
//        loginResult.setCode(ResultCode.FAILED_LOGIN.getCode());
//        loginResult.setMsg(ResultCode.FAILED_LOGIN.getMsg());
      //  return  loginResult;
        return R.fail(ResultCode.FAILED_LOGIN);
    }

    @Override
    public int add(SysUserSaveDTO sysUserSaveDTO) {

        //那么考虑一下啊是否我需要判断一下账号或者密码它符合某种格式，我不能说是我的密码就一个数字也可以吧

        //我们需要判断，不能有重复的账号名，
        List<SysUser> sysUserList=sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getUserAccount,sysUserSaveDTO.getUserAccount()));
        if(CollectionUtils.isNotEmpty(sysUserList)){
            //用户已经存在，爆一个问题,提供一个自定义异常，因为Runtime没有合适构造方法
            throw new ServiceException(ResultCode.AILED_USER_EXISTS);
        }
        //优化:看是否有重复,不要光看眼前，往后想一想
        //将DTO转为实体,为啥不直接用实体呢？
        SysUser sysUser=new SysUser();
        sysUser.setUserAccount(sysUserSaveDTO.getUserAccount());
        sysUser.setPassword(BCryptUtils.encryPassword(sysUserSaveDTO.getPassword()));
        //新增一条数据，那么肯定是当前数据和当前时间，根据数据库的返回值判断成功或者失败
       return  sysUserMapper.insert(sysUser);

    }

    @Override
    public R<LoginUserVO> info(String token) {
        if (StrUtil.isNotEmpty(token) &&
                token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
         LoginUser loginUser=tokenService.getLoginUser(token,secret);
         if(loginUser==null){
             return R.fail();
         }
         LoginUserVO loginUserVO=new LoginUserVO();
         loginUserVO.setNickName(loginUser.getNickName());
        return R.ok(loginUserVO);
    }

    @Override
    public boolean logout(String token) {
        if (StrUtil.isNotEmpty(token) &&
                token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return tokenService.deleteLoginUser(token,secret);

    }
    //编译时异常，没有处理，编译时候的异常/运行时候的异常，如byZero，outBound,
    // try catch的异常处理，过于冗长，以及，我需要给一大块都给他try里面，丑
    //1.引入一个全局异常处理方案，不用使用try catch,       //日志框架（2.当捕获到异常时候，记录相关日志，作为问题排查的线索






}
